package org.budo.support.spring.bean.factory.support;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.budo.support.assertion.Assert;
import org.budo.support.lang.util.StringUtil;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * @author limingwei
 */
@Getter
@Setter
@Accessors(fluent = true)
@ToString
public class BeanBuilder {
    private String id;

    private String type;

    private String parent;

    private int _constructorArgIndex = 0;

    /**
     * 有序的map
     */
    private Map<String, Object> propertyValues = new LinkedHashMap<String, Object>();

    /**
     * 有序的map
     */
    private Map<Integer, Object> constructorArgs = new LinkedHashMap<Integer, Object>();

    private BeanDefinitionRegistry beanDefinitionRegistry;

    public BeanBuilder type(String type) {
        this.type = type;
        return this;
    }

    public BeanBuilder type(Class<?> type) {
        return this.type(type.getName());
    }

    public BeanBuilder property(String propertyName, Object propertyValue) {
        return this.propertyValue(propertyName, propertyValue);
    }

    public BeanBuilder propertyValue(String propertyName, Object propertyValue) {
        this.propertyValues.put(propertyName, propertyValue);
        return this;
    }

    public BeanBuilder constructorArg(Object value) {
        this.constructorArgs.put(this._constructorArgIndex++, value);
        return this;
    }

    public BeanBuilder constructorArgReference(String refBeanName) {
        return this.constructorArg(new RuntimeBeanReference(refBeanName));
    }

    public BeanBuilder ref(String propertyName, String refBeanName) {
        return this.propertyReference(propertyName, refBeanName);
    }

    public BeanBuilder propertyReference(String propertyName, String refBeanName) {
        return this.propertyValue(propertyName, new RuntimeBeanReference(refBeanName));
    }

    public BeanBuilder propertyReferences(Map<String, String> propertyReferences) {
        Assert.notNull(propertyReferences, "propertyReferences is null");

        for (Entry<String, String> entry : propertyReferences.entrySet()) {
            this.propertyReference(entry.getKey(), entry.getValue());
        }

        return this;
    }

    /**
     * 获取这个Bean,才会执行InitializingBean等相关方法
     */
    public Object get() {
        if (null == this.beanDefinitionRegistry) {
            throw new RuntimeException("#97 beanDefinitionRegistry is null, this=" + this);
        }

        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) this.beanDefinitionRegistry;
        return defaultListableBeanFactory.getBean(this.id());
    }

    public BeanBuilder registerTo(BeanDefinitionRegistry beanDefinitionRegistry) {
        this.beanDefinitionRegistry = beanDefinitionRegistry;

        Assert.notNull(beanDefinitionRegistry, "#67 beanDefinitionRegistry must not be null");
        beanDefinitionRegistry.registerBeanDefinition(this.id(), this.toDefinition());
        return this;
    }

    public BeanBuilder registerTo(ApplicationContext applicationContext) {
        Assert.notNull(applicationContext, "#71 applicationContext must not be null");

        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();

        return this.registerTo((BeanDefinitionRegistry)beanFactory);
    }

    public BeanBuilder registerTo(ConfigurableListableBeanFactory beanFactory) {
        return this.registerTo((BeanDefinitionRegistry)beanFactory);
    }

    public AbstractBeanDefinition toDefinition() {
        if (StringUtil.isEmpty(this.type()) && StringUtil.isEmpty(this.parent())) {
            throw new IllegalArgumentException("#109 type and parent are both null or empty, this=" + this);
        }

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(this.type());

        if (null != this.parent()) {
            beanDefinitionBuilder.setParentName(this.parent());
        }

        if (null != this.propertyValues()) {
            for (String key : this.propertyValues().keySet()) {
                beanDefinitionBuilder.addPropertyValue(key, this.propertyValues().get(key));
            }
        }

        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();

        if (null != this.constructorArgs()) {
            for (Integer key : this.constructorArgs().keySet()) {
                Object value = this.constructorArgs().get(key);
                beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(key, value);
            }
        }

        return beanDefinition;
    }
}